/////////////////////////////////////////////////////////////////////////////
// Name:        xwafer/life_readline.cpp
// Purpose:     XWafer-Read Line Mapp
// Author:      Long Zou
// Modified by:
// Created:     July/2018
// Copyright:   (c) 2018, Kiloseed Co.,Ltd
// Licence:     Commeric Licence
/////////////////////////////////////////////////////////////////////////////

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#ifndef WIN32
#include <unistd.h>
#include <sys/mman.h>
#endif
#include <sys/stat.h>
#include <errno.h>
#include "kWooka_readline.h"


#ifndef WXSTRDUP
#ifdef WIN32
#define WXSTRDUP(x) _strdup(x)
#else
#define WXSTRDUP(x) strdup(x)
#endif
#endif

bool path_of_file(char* pbuf, size_t buflen, const char* psrc) {
    size_t len = strlen(psrc);
    size_t xl = len;
    for(size_t t = len - 1; t > 0; t --) {
#ifdef WIN32
        if (psrc[t] == '\\') {
            xl = t;
            break;
        }
#else
        if (psrc[t] == '/') {
            xl = t;
            break;
        }
#endif
    }
    
    if (xl > buflen) {
        return false;
    }
    memcpy(pbuf, psrc, xl);
    return true;
}

/**
 * Open the file
 **/
LifeFile* life_fileopen(const char* filename){
    struct stat buf = {0};
    
    
    LifeFile* lf = (LifeFile*) malloc(sizeof(LifeFile));
    if (lf == NULL) {
        perror("Allocate LifeFile struct failed.");
        return NULL;
    }
    
    stat(filename, &buf);
    lf->file_size = buf.st_size;
    
#ifdef WIN32
    lf->fd = CreateFileA(filename,
                         GENERIC_READ,
                         0,
                         NULL,
                         OPEN_EXISTING,
                         FILE_ATTRIBUTE_NORMAL,
                         NULL);
    if (lf->fd == INVALID_HANDLE_VALUE || lf->fd == NULL) {
        free(lf);
        return NULL;
    }
    
    lf->maphandle = CreateFileMapping(lf->fd, NULL, PAGE_READONLY, 0, 0, NULL);
    
    if (lf->maphandle == INVALID_HANDLE_VALUE) {
        CloseHandle(lf->fd);
        free(lf);
        return NULL;
    }
    
    lf->map_addr = MapViewOfFile(lf->maphandle, FILE_MAP_READ, 0, 0, lf->file_size);
    
    if (lf->map_addr == NULL) {
        CloseHandle(lf->maphandle);
        CloseHandle(lf->fd);
        free(lf);
        return NULL;
    }
#else
    lf->fd = open(filename, O_RDONLY);
    if (lf->fd == -1) {
        perror("Can not open for readonly.");
        free(lf);
        return NULL;
    }
    lf->map_addr = mmap(NULL, lf->file_size, PROT_READ, MAP_SHARED, lf->fd, 0);
    if (lf->map_addr == MAP_FAILED) {
        perror("Can not map the file for shared read mode.");
        close(lf->fd);
        free(lf);
        return NULL;
    }
#endif
    
    lf->pointer = (unsigned char*) lf->map_addr;
    lf->offset  = 0;
    return lf;
}

/***
 * 关闭文件
 ***/
void life_fileclose(LifeFile* lf) {
    if (lf != NULL) {
#ifdef WIN32
        if (lf->map_addr != NULL) {
            UnmapViewOfFile(lf->map_addr);
        }
        if (lf->maphandle != INVALID_HANDLE_VALUE && lf->maphandle != NULL){
            CloseHandle(lf->maphandle);
        }
        if (lf->fd != INVALID_HANDLE_VALUE && lf->fd != NULL){
            CloseHandle(lf->fd);
        }
#else
        if (lf->map_addr != NULL) {
            if (lf->map_addr != MAP_FAILED) {
                if (munmap(lf->map_addr, lf->file_size) != 0) {
                    printf("Unmap Address 0x%p failed. error: %d, msg: %s.\n", lf->map_addr, errno, strerror(errno));
                }
            }
        }
        
        if (lf->fd != -1) {
            close(lf->fd);
        }
#endif
        
        free(lf);
    }
}

int life_fileend(LifeFile* lf){
    if (lf == NULL) {
        return 0;
    }
    return lf->offset >= lf->file_size;
}

int life_progress(LifeFile* lf) {
    if (lf == NULL) {
        return 0;
    }
    return (int) (100.0 * ((float) lf->offset / (float) lf->file_size));
}

size_t  life_readline(LifeFile* lf, unsigned char* buff, size_t len){
    size_t ll = len;
    if (lf->offset + len > lf->file_size) {
        ll = lf->file_size - lf->offset;
    }
    
    for(size_t ii = 0; ii < ll; ii ++) {
        if (lf->pointer[ii] != '\n') {
            buff[ii] = lf->pointer[ii];
        } else {
            lf->pointer += ii + 1; //Skip this \n
            lf->offset += ii + 1;  //Skip this \n
            buff[ii] = '\0';
            
            for (size_t kk = ii - 1; kk > 0; kk --) {
                if (buff[kk] == '\r' || buff[kk] == '\n') {
                    buff[kk] = '\0';
                } else {
                    break;
                }
            }
            
            return ii;
        }
    }
    lf->pointer += ll;
    lf->offset += ll;
    return ll;
}


int life_startwith(const char* mbstr, const char* compared) {
    size_t len = strlen(compared);
    if (strlen(mbstr) < len){
        return 0;
    }
    
    if (memcmp(compared, mbstr, len) == 0){
        return 1;
    }
    
    return 0;
}


int life_split_ns(char ***dest, int *count, char *s_str, const char **separator) {
    int ns = 0;
    while(separator[ns] != NULL) {
        ns ++;
    }
    return life_split(dest, count, s_str, separator, ns, 0, 0);
}


/**
 * Split a string into some strings according to a list of separators.
 *
 * @Param dest                      out: storage the strings has be split.
 * @Param count                     out: the number of strings has be split successfully, 0 for failed to split.
 * @Param s_str                     in:  the strings for split.
 * @Param separator                 in:  the list of split separators.
 * @Param number_separators         in:  the numbers of separators.
 * @Param compress_separator        in:  will be create a empty string when two split adjacent
 *                                       if compress_separator > 0 and not for compress_separator == 0
 * @Param keep_separator            in:  the separators will be put into parameter 'dest' if keep_separator > 0
 */
int life_split(char ***dest, int *count, char *s_str, const char **separator, int number_separators, int compress_separator, int keep_separator)
{
    unsigned int i = 0;
    char **result = NULL;
    char **temp_result = NULL;
    unsigned int curt_size = 0;
    unsigned int new_size = 0;
    char *look_ahead = NULL;
    char *most_front_separator_start = NULL;
    char *most_front_separator_end = NULL;
    char *separator_start = NULL;
    //int find_a_separator = 0;
    int find_a_string = 0;
    
    *count = 0;
    *dest = NULL;
    
    /* check parameters */
    if (
        dest == NULL
        || s_str == NULL || *s_str == '\0'
        || separator == NULL
        || number_separators <= 0
        || compress_separator < 0
        || keep_separator < 0
        )
        return -1;
    
    for (i = 0; i < (unsigned int) number_separators; i++)
        if (separator[i] == NULL || *separator[i] == '\0')
            return -1;
    
    for (look_ahead = s_str; *look_ahead != '\0'; look_ahead = most_front_separator_end)
    {
        most_front_separator_start = look_ahead + strlen(look_ahead);
        most_front_separator_end = look_ahead + strlen(look_ahead);
        //find_a_separator = 0;
        
        /* find the next separator. */
        for (i = 0; i < (unsigned int)number_separators; i++)
        {
            separator_start = strstr(look_ahead, separator[i]);
            if (separator_start == NULL)
                continue;
            
            //find_a_separator = 1;
            /* update the most front separator. */
            if (separator_start < most_front_separator_start)
            {
                most_front_separator_start = separator_start;
                most_front_separator_end = most_front_separator_start + strlen(separator[i]);
            }
        }
        
        find_a_string = (look_ahead == most_front_separator_start) ? 0 : 1;
        
        /* allow put the new string into result if need. */
        new_size = (find_a_string > 0) ? (curt_size + 1) : ((compress_separator > 0) ? curt_size : (curt_size + 1));
        /* allow put the separator into result if need. */
        new_size = (keep_separator > 0) ? (new_size + 1) : new_size;
        if (new_size == curt_size)
            continue;
        
        temp_result = (char **)malloc((new_size) * sizeof(char *));
        if (temp_result == NULL)
        {
            if (result != NULL)
            {
                for (i = 0; i < curt_size; i++)
                    if (result[i] != NULL)
                        free(result[i]);
                free(result);
                result = NULL;
            }
            
            return -2;
        }
        
        /* copy the pointers of string find early. */
        memset(temp_result, 0, new_size);
        for (i = 0; i < curt_size; i++)
            temp_result[i] = result[i];
        
        if (find_a_string == 0)
        {
            if (compress_separator == 0)
            {
                temp_result[curt_size] = (char *)malloc(sizeof(char));
                if (temp_result[curt_size] == NULL)
                {
                    if (temp_result != NULL)
                    {
                        for (i = 0; i < curt_size; i++)
                            if (temp_result[i] != NULL)
                                free(temp_result[i]);
                        free(temp_result);
                        temp_result = NULL;
                    }
                    
                    return -2;
                }
                memset(temp_result[curt_size], 0, 1);
            }
        }
        else
        {
            /* put the new string into result. */
            temp_result[curt_size] = (char *)malloc((most_front_separator_start - look_ahead + 1) * sizeof(char));
            if (temp_result[curt_size] == NULL)
            {
                if (temp_result != NULL)
                {
                    for (i = 0; i < curt_size; i++)
                        if (temp_result[i] != NULL)
                            free(temp_result[i]);
                    free(temp_result);
                    temp_result = NULL;
                }
                
                return -2;
            }
            memset(temp_result[curt_size], 0, most_front_separator_start - look_ahead + 1);
            strncpy(temp_result[curt_size], look_ahead, most_front_separator_start - look_ahead);
            temp_result[curt_size][most_front_separator_start - look_ahead] = '\0';
        }
        
        if (keep_separator > 0)
        {
            /* put the separator into result. */
            temp_result[new_size - 1] = (char *)malloc(most_front_separator_end - most_front_separator_start + 1);
            if (temp_result[new_size - 1] == NULL)
            {
                if (temp_result != NULL)
                {
                    for (i = 0; i < new_size - 1; i++)
                        if (temp_result[i] != NULL)
                            free(temp_result[i]);
                    free(temp_result);
                    temp_result = NULL;
                }
                
                return -2;
            }
            memset(temp_result[new_size - 1], 0, most_front_separator_end - most_front_separator_start + 1);
            strncpy(temp_result[new_size - 1], most_front_separator_start, most_front_separator_end - most_front_separator_start);
            temp_result[new_size - 1][most_front_separator_end - most_front_separator_start] = '\0';
        }
        
        /* update result. */
        free(result);
        result = temp_result;
        temp_result = NULL;
        curt_size = new_size;
    }
    
    *dest = result;
    *count = curt_size;
    
    return 0;
}


int life_freesplit(char **dest, int count){
    if (dest != NULL) {
        for( int i = 0; i < count; i ++) {
            free(dest[i]);
        }
        free(dest);
    }
    return 0;
}
